#include <Render/Include/RendererDX11.h>

#include <Render/Include/MeshDX11.h>
#include <Render/Include/TextureDX11.h>

#include <D3DX11async.h>

#include <math.h>

using namespace Render;

// ========== LIFECYCLE ========== //

RendererDX11::RendererDX11(IWinWindow* window)
    :Renderer()
{
    HRESULT hr = S_OK;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = sizeof( driverTypes ) / sizeof( driverTypes[0] );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = window->getWidth();
    sd.BufferDesc.Height = window->getHeight();
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = window->getHWND();
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
   
    for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
    {
        D3D_FEATURE_LEVEL featureLevelsRequested = D3D_FEATURE_LEVEL_10_1; //D3D_FEATURE_LEVEL_11_0;
        UINT numFeatureLevelsRequested = 1;
        D3D_FEATURE_LEVEL featureLevelsSupported;
        hr = D3D11CreateDeviceAndSwapChain(NULL, driverTypes[driverTypeIndex], NULL, createDeviceFlags, &featureLevelsRequested, numFeatureLevelsRequested,
                                D3D11_SDK_VERSION, &sd, &(this->swap_chain), &(this->device), &featureLevelsSupported, &(this->immediate_context));
        if (SUCCEEDED(hr))
        {
            break;
        }        
    }

    if( FAILED( hr ) )
    {
        throw -1; 
    }

    // Create a render target view
    ID3D11Texture2D* pBuffer;
    hr = this->swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*) &pBuffer);
    if (FAILED(hr))
    {
        throw -1;
    }

    this->render_target_view = NULL;
    hr = this->device->CreateRenderTargetView(pBuffer, NULL, &this->render_target_view);
    pBuffer->Release();
    if (FAILED(hr))
    {
        throw -1;
    }

    this->depth_stencil_view = NULL;
    // Create a depth stencil
    ID3D11Texture2D* depth_stencil_buffer;
    D3D11_TEXTURE2D_DESC depthStencilDesc;
    ZeroMemory( &depthStencilDesc, sizeof(depthStencilDesc) );
    depthStencilDesc.Width = window->getWidth();
    depthStencilDesc.Height = window->getHeight();
    depthStencilDesc.MipLevels = 1;
    depthStencilDesc.ArraySize = 1;
    depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilDesc.SampleDesc.Count = 1;
    depthStencilDesc.SampleDesc.Quality = 0;
    depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
    depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthStencilDesc.CPUAccessFlags = 0;
    depthStencilDesc.MiscFlags = 0;

    hr = this->device->CreateTexture2D(&depthStencilDesc, NULL, &depth_stencil_buffer);
    if (FAILED(hr))
    {
        throw -1;
    }
    
    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &descDSV, sizeof(descDSV) );
    descDSV.Format = depthStencilDesc.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = this->device->CreateDepthStencilView(depth_stencil_buffer, &descDSV, &this->depth_stencil_view);
    depth_stencil_buffer->Release();
    if (FAILED(hr))
    {
        throw -1;
    }
    
    /// initializes de viewport
    this->viewport.Width = (float)window->getWidth();
    this->viewport.Height = (float)window->getHeight();
    this->viewport.MinDepth = 0.0f;
    this->viewport.MaxDepth = 1.0f;
    this->viewport.TopLeftX = 0.0f;
    this->viewport.TopLeftY = 0.0f;

    /// configures immediate context
    this->immediate_context->OMSetRenderTargets(1, &this->render_target_view, this->depth_stencil_view);
    this->immediate_context->RSSetViewports(1, &this->viewport);

    // Compile the shaders
    
    ID3DBlob* pVSBlob = NULL;
    //bool compile_success = this->compileShaderFromFile( L"..\\Render\\Shader\\DX11Effect.hlsl", "VS", "vs_4_0", &pVSBlob );
    bool compile_success = this->compileShaderFromFile( L"DX11Effect.hlsl", "VS", "vs_4_0", &pVSBlob );
    if( !compile_success )
    {
        MessageBox( NULL,
            L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        throw -1;
    }

    // Create the vertex shader
    hr = this->device->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &this->vertex_shader );
    if( FAILED( hr ) )
    {    
        pVSBlob->Release();
        throw -1;
    }

    // Define the input layout
    D3D11_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }
    };
    UINT numElements = ARRAYSIZE( layout );
    // Create the input layout
    hr = this->device->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(),
        pVSBlob->GetBufferSize(), &this->input_layout );
    pVSBlob->Release();
    if( FAILED( hr ) )
    {
        throw -1;
    }

    // Set the input layout
    this->immediate_context->IASetInputLayout( this->input_layout );

    // Compile the pixel shader
    ID3DBlob* pPSBlob = NULL;
    // compile_success = this->compileShaderFromFile( L"..\\Render\\Shader\\DX11Effect.hlsl", "PS", "ps_4_0", &pPSBlob );
    compile_success = this->compileShaderFromFile( L"DX11Effect.hlsl", "PS", "ps_4_0", &pPSBlob );
    if( !compile_success )
    {
        MessageBox( NULL,
            L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        throw -1;
    }

    // Create the pixel shader
    hr = this->device->CreatePixelShader( pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &this->pixel_shader );
    pPSBlob->Release();
    if( FAILED( hr ) )
    {
        throw -1;
    }

    // Create both constant buffer
    D3D11_BUFFER_DESC bd;
    ZeroMemory( &bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(ConstantBufferTransformationData);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    hr = this->device->CreateBuffer( &bd, NULL, &this->constant_buffer_transformation );
    if( FAILED( hr ) )
    {
        throw -1;
    }

    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(ConstantBufferLightingData);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    hr = this->device->CreateBuffer( &bd, NULL, &this->constant_buffer_lighting );
    if( FAILED( hr ) )
    {
        throw -1;
    }

    // Create the sample state
    D3D11_SAMPLER_DESC sampler_description;
    ZeroMemory( &sampler_description, sizeof(sampler_description) );
    sampler_description.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampler_description.AddressU = D3D11_TEXTURE_ADDRESS_MIRROR;
    sampler_description.AddressV = D3D11_TEXTURE_ADDRESS_MIRROR;
    sampler_description.AddressW = D3D11_TEXTURE_ADDRESS_MIRROR;
    sampler_description.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampler_description.MinLOD = 0;
    sampler_description.MaxLOD = D3D11_FLOAT32_MAX;
    hr = this->device->CreateSamplerState(&sampler_description, &this->sampler_state );
    if( FAILED( hr ) )
    {
        throw -1;
    }

}

RendererDX11::~RendererDX11(void)
{
    this->device->Release();
    this->immediate_context->Release();
    this->swap_chain->Release();
    this->render_target_view->Release();
    this->depth_stencil_view->Release();
    this->vertex_shader->Release();
    this->pixel_shader->Release();
    this->input_layout->Release();
    this->constant_buffer_transformation->Release();
    this->constant_buffer_lighting->Release();
}

// ========== OVERLODED OPERATORS ========== //

// ========== METHODS ========== //

void RendererDX11::renderTorus(AppModel::Torus* torus)
{
    float clear_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // red, green, blue, alpha

    /// configures immediate context
    this->immediate_context->OMSetRenderTargets(1, &this->render_target_view, this->depth_stencil_view);
    
    this->immediate_context->ClearRenderTargetView(this->render_target_view, clear_color);
    this->immediate_context->ClearDepthStencilView(this->depth_stencil_view, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
  
    MeshDX11* mesh = dynamic_cast<MeshDX11*>(torus->getMesh());
    TextureDX11* texture = dynamic_cast<TextureDX11*>(torus->getTexture());
    // set the vertex buffer
    ID3D11Buffer* vertex_buffer = mesh->getVertexBuffer();
    UINT stride = mesh->getVertexSizeStride();
    UINT offset = mesh->getVertexOffset();
    this->immediate_context->IASetVertexBuffers( 0, 1, &vertex_buffer, &stride, &offset );
    this->immediate_context->IASetInputLayout( this->input_layout );

    // Set index buffer
    ID3D11Buffer* index_buffer = mesh->getIndexBuffer();
    this->immediate_context->IASetIndexBuffer( index_buffer, DXGI_FORMAT_R32_UINT, 0 );

    // Set primitive topology
    this->immediate_context->IASetPrimitiveTopology( mesh->getPrimitiveType() );

    /// set the transformation matrix for the element
    D3DXMATRIX world_matrix;
    D3DXMatrixIdentity(&world_matrix);

    ConstantBufferTransformationData transformation_data;
    transformation_data.world = world_matrix;

    D3DXMATRIX view_matrix;
    D3DXVECTOR3 camera_position;
    D3DXVECTOR3 camera_at;
    D3DXVECTOR3 camera_up;
    float camera_pos_x = this->arc_ball_camera_radio * cos(this->arc_ball_camera_theta) * cos (this->arc_ball_camera_phi);
    float camera_pos_y = this->arc_ball_camera_radio * sin(this->arc_ball_camera_theta) * cos (this->arc_ball_camera_phi);
    float camera_pos_z = this->arc_ball_camera_radio * sin (this->arc_ball_camera_phi);
    camera_position = D3DXVECTOR3( camera_pos_x, camera_pos_y, camera_pos_z);
    camera_at = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    camera_up = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

    D3DXMatrixLookAtLH(&view_matrix, &camera_position, &camera_at, &camera_up);
    transformation_data.view = view_matrix;

    /// set the projection
    D3DXMATRIX projection_matrix;
    D3DXMatrixPerspectiveFovLH(&projection_matrix, 3.14f/3.0f, 1.0f, 1.0f, 100.0f);

    transformation_data.projection = projection_matrix;  

    D3DXMATRIX normal_matrix;
    D3DXMatrixMultiply(&normal_matrix, &world_matrix, &view_matrix);
    float determinant;
    D3DXMatrixInverse(&normal_matrix, &determinant, &normal_matrix);
    D3DXMatrixTranspose(&normal_matrix, &normal_matrix);
    transformation_data.normal = normal_matrix;

    transformation_data.wave_offset.x = (float)(torus->getWaveOffset());

    // Set the constant buffer with matrix transformations
    this->immediate_context->UpdateSubresource( this->constant_buffer_transformation, 0, NULL, &(transformation_data), 0, 0 );

    ConstantBufferLightingData lighting_data;
    lighting_data.ambient_material_color = D3DXVECTOR4(0.05f, 0.05f, 0.05f, 1.0f);
    lighting_data.diffuse_material_color = D3DXVECTOR4(0.45f, 0.45f, 0.45f, 1.0f);
    lighting_data.specular_material_color = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
    lighting_data.light_color = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
    lighting_data.light_dir = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);//-camera_position;
    lighting_data.material_specular_power = 10.0f;

    // Set the constant buffer with lighting data
    this->immediate_context->UpdateSubresource( this->constant_buffer_lighting, 0, NULL, &(lighting_data), 0, 0 );

    ID3D11ShaderResourceView* resource_view = texture->getD3D11ShaderResourceView();
    this->immediate_context->VSSetShader( this->vertex_shader, NULL, 0 );
    this->immediate_context->VSSetConstantBuffers( 0, 1, &this->constant_buffer_transformation );
    this->immediate_context->VSSetConstantBuffers( 1, 1, &this->constant_buffer_lighting );
    this->immediate_context->PSSetShader( this->pixel_shader, NULL, 0 );
    this->immediate_context->PSSetConstantBuffers( 0, 1, &this->constant_buffer_transformation );
    this->immediate_context->PSSetShaderResources( 0, 1, &resource_view );
    this->immediate_context->PSSetSamplers( 0, 1, &this->sampler_state );
    this->immediate_context->DrawIndexed( mesh->getIndexCount(), 0, 0 );
    
    this->swap_chain->Present( 0, 0);
}

Mesh* RendererDX11::createMesh(VertexData* vertex_data, const unsigned int &vertex_count, IndexData* index_data, const unsigned int &index_count)
{
    D3D11_BUFFER_DESC bd;
    ZeroMemory( &bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( VertexData ) * vertex_count;
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory( &InitData, sizeof(InitData) );
    InitData.pSysMem = vertex_data;
    ID3D11Buffer* vertex_buffer;
    HRESULT hr = this->device->CreateBuffer( &bd, &InitData, &vertex_buffer );
    if( FAILED( hr ) )
    {
        throw -1;
    }

    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( unsigned int ) * index_count;
    bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = 0;
    InitData.pSysMem = index_data;
    ID3D11Buffer* index_buffer;
    hr = this->device->CreateBuffer( &bd, &InitData, &index_buffer );
    if( FAILED( hr ) )
    {
        throw -1;
    }

    MeshDX11* mesh = new MeshDX11(vertex_buffer, sizeof(VertexData), 0, index_buffer, index_count, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    return mesh;
}

Texture* RendererDX11::createTexture(const char* file)
{
    ID3D11Texture2D * textura2D = NULL;
    ID3D11ShaderResourceView *shader_resource_view = NULL;
    D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_view_description;
    D3D11_TEXTURE2D_DESC textura2D_description;

    std::wstring unicode_text_file = this->convertToWideString(std::string(file));
    // Load the texture and initialize an ID3D11Texture2D object.
    D3DX11CreateTextureFromFile(this->device, unicode_text_file.c_str(), NULL, NULL, (ID3D11Resource**)&textura2D, NULL);

    // Get a texture description to determine the texture
    // format of the loaded texture.
    textura2D->GetDesc(&textura2D_description);

    // Fill in the D3D11_SHADER_RESOURCE_VIEW_DESC structure.
    shader_resource_view_description.Format = textura2D_description.Format;
    shader_resource_view_description.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    shader_resource_view_description.Texture2D.MostDetailedMip = 0;
    shader_resource_view_description.Texture2D.MipLevels = textura2D_description.MipLevels;

    // Create the shader resource view.
    HRESULT hr = this->device->CreateShaderResourceView(textura2D, &shader_resource_view_description, &shader_resource_view);
    if (FAILED (hr))
    {
        throw -1;
    }

    TextureDX11* texture = new TextureDX11(textura2D, textura2D_description, shader_resource_view);
    return texture;
}

// ========== GETTERS ========== //

// ========== SETTERS ========== //

// ========== INQUIRY ========== //

// ========== INTERNAL METHODS ========== //

bool RendererDX11::compileShaderFromFile( WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut )
{
    bool result_ok = true;

    DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
    // Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
    // Setting this flag improves the shader debugging experience, but still allows 
    // the shaders to be optimized and to run exactly the way they will run in 
    // the release configuration of this program.
    dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

    ID3DBlob* pErrorBlob;
    HRESULT hr = D3DX11CompileFromFile( szFileName, NULL, NULL, szEntryPoint, szShaderModel, 
        dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL );
    if( FAILED(hr) )
    {
        if( pErrorBlob != NULL )
            OutputDebugStringA( (char*)pErrorBlob->GetBufferPointer() );
        if( pErrorBlob ) pErrorBlob->Release();
        return false;
    }
    if( pErrorBlob ) pErrorBlob->Release();

    return true;
}

std::wstring RendererDX11::convertToWideString(const std::string &cadena)
{
    int len;
    int slength = (int)cadena.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, cadena.c_str(), slength, 0, 0);
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, cadena.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
}
